/*
 * MacroParser.java
 *
 * Created on August 8, 2007, 1:38 AM
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


package tal.drivechain.chain.script;

import java.io.File;
import tal.drivechain.variable.VarTable;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import tal.drivechain.parsing.Tokenizer;
import tal.drivechain.expression.dcExpression;
import tal.drivechain.parsing.Range;
import tal.drivechain.variable.VarTableAction;
import tal.drivechain.variable.Variable;
import tal.drivechain.variable.types.DataType;

/**
 *
 * @author Main
 */
public class ScriptMacroParser {
    private static Pattern macroPattern = Pattern.compile("%[^%]+%");
    private HashMap<String, String> args;
    
    public ScriptMacroParser(HashMap<String, String> args) {
        this.args = args;
    }
    
    public String parse(String str) throws IllegalArgumentException {
        String parsed = str;
        parsed = parseIncludes(parsed);
        parsed = parseDefines(parsed);
        while (firstMacro(parsed, 0)!=null) {
            MacroCommand c = firstMacro(parsed, 0);
            if (c.getName().equals("@if")) {
                parsed = parseIf(parsed, c);
            } else if (c.getName().equals("@iterate")) {
                parsed = parseIterate(parsed, c);
            } else parsed = parseVar(parsed, c);
        }
        return parsed;
    }
    
    private MacroCommand firstMacro(String str, int start) {
        Matcher m = macroPattern.matcher(str);
        if (m.find(start)) return new MacroCommand(str, new int[] {m.start(), m.end()});
        else return null;
    }
    
    private MacroCommand findMacro(String str, String name, int start) {
        Matcher m = macroPattern.matcher(str);
        while (m.find()) {
            String macro = m.group();
            if (macro.startsWith("%" + name + " ") && m.start()>=start)
                return new MacroCommand(str,new int[] { m.start(), m.end() });
        }
        return null;
    }

    private String parseIf(String parsed, MacroCommand startIf) throws IllegalArgumentException {
        //first find the real &if of the first if (the one that has a balanced ifs and &ifs 
        //inside it. maybe just add an if name like %@if bpmif bpm%...%&if bpmif%
        if (startIf.getArgs()==null || startIf.getArgs().length!=2)
            throw new IllegalArgumentException("\'If\' script statement must have 2 arguments: name and condition expression.");
        String ifname = startIf.getArgs()[0].trim().toLowerCase();
        MacroCommand endIf = null, findEndif = null;
        int lastIfEnd = startIf.getLocation()[1];
        boolean foundEndIf = false;
        while (!foundEndIf) {
            findEndif = findMacro(parsed, "&if", lastIfEnd);
            if (findEndif!=null) {
                int thisIfEnd = findEndif.getLocation()[1];
                if (thisIfEnd==lastIfEnd) break;
                else lastIfEnd = thisIfEnd;
                if (findEndif.getArgs()!=null && findEndif.getArgs().length>0
                        && findEndif.getArgs()[0].toLowerCase().trim().equals(ifname)) {
                    endIf = findEndif;
                    foundEndIf = true;
                }
            } else break;
        }
        if (!foundEndIf) throw new IllegalArgumentException("Encountered an @if " + ifname + " macro command without a matching &if " + ifname + " command.");
        VarTable vars = new VarTable();

        for (String name : args.keySet()) {
            VarTableAction action = VarTableAction.parseAction(VarTableAction.Action.SET_ANY, name);
            action.set(new Variable(name, DataType.STRING, args.get(name)), vars, null);
        }
        dcExpression condition = new dcExpression(startIf.getArgs()[1], DataType.BOOLEAN);
        boolean conditionMet = (Boolean)condition.evaluate();
        String replacement = "";
        if (conditionMet) replacement = parsed.substring(startIf.getLocation()[1], endIf.getLocation()[0]);
        return parsed.substring(0, startIf.getLocation()[0]) + replacement + parsed.substring((endIf.getLocation()[1]));
    }

    private String parseIterate(String parsed, MacroCommand startIterate) throws IllegalArgumentException {
        if (startIterate.getArgs()==null || startIterate.getArgs().length!=3)
            throw new IllegalArgumentException("\'iterate\' script statement must have 3 arguments: name, variable name and range expression.");
        String iterateName = startIterate.getArgs()[0].trim().toLowerCase();
        MacroCommand endIterate = null, findEndIterate = null;
        int lastIterateEnd = startIterate.getLocation()[1];
        boolean foundEndIterate = false;
        while (!foundEndIterate) {
            findEndIterate = findMacro(parsed, "&iterate", lastIterateEnd);
            if (findEndIterate!=null) {
                int thisIterateEnd = findEndIterate.getLocation()[1];
                if (thisIterateEnd==lastIterateEnd) break;
                else lastIterateEnd = thisIterateEnd;
                if (findEndIterate.getArgs()!=null && findEndIterate.getArgs().length>0
                        && findEndIterate.getArgs()[0].toLowerCase().trim().equals(iterateName)) {
                    endIterate = findEndIterate;
                    foundEndIterate = true;
                }
            } else break;
        }
        if (!foundEndIterate) throw new IllegalArgumentException("Encountered an @iterate " + iterateName + " macro command without a matching &iterate " + iterateName + " command.");
        HashMap<String, String> iterateArgs = (HashMap<String,String>)args.clone();
        String varname = startIterate.getArgs()[1];
        Range range = new Range(startIterate.getArgs()[2], Range.Type.CLOSED_ONLY);
        double[] drange = range.getRange();
        String iterateSection = parsed.substring(startIterate.getLocation()[1], endIterate.getLocation()[0]);        
        String replacement = "";
        int start = (int)drange[0];
        int end = (int)drange[1];
        for (int i=start; i<=end; i = i + range.getDirection()) {
            iterateArgs.put(varname, ""+i);
            ScriptMacroParser iterateParser = new ScriptMacroParser(iterateArgs);
            replacement+=iterateParser.parse(iterateSection);
        }

        return parsed.substring(0, startIterate.getLocation()[0]) + replacement + parsed.substring((endIterate.getLocation()[1]));

    }

    private String parseVar(String parsed, MacroCommand var) throws IllegalArgumentException {
        //check exists in args if not look for a default value arg
        String replacement = args.get(var.getName());
        if (replacement == null) {
            if (var.getArgs()!=null) replacement = var.getArgs()[0];
            else throw new IllegalArgumentException("script variable " + var.getName() + " has no default value. Supply a value for it.");
        } 
        return parsed.substring(0, var.getLocation()[0]) + replacement + parsed.substring((var.getLocation()[1]));
    }

    private String parseIncludes(String parsed) {
        /*String ret = parsed;
        MacroCommand include = findMacro(parsed, "@include", 0);
        int lastIncludeEnd = 0;
        while (include!=null) {
            if (include!=null) {
                lastIncludeEnd = include.getLocation()[1];
                if (include.getArgs().length!=1)
                    throw new IllegalArgumentException("@include script commands must have exactly 1 arguments - include file name.");
                ScriptCompiler includeFile = new ScriptCompiler();
                String replacement = includeFile.loadFromFile(ScriptCompiler.getScriptFile(include.getArgs()[0], new File(System.getProperty("user.dir")), false));
                ret = ret.substring(0, include.getLocation()[0]) + replacement + ret.substring((include.getLocation()[1]));
            } 
            include = findMacro(parsed, "@include", lastIncludeEnd);            
        }
        return ret;*/
        return null;
    }

    private String parseDefines(String parsed) {
        String ret = parsed;
        MacroCommand define = findMacro(parsed, "@define", 0);
        int lastDefineEnd = 0;
        while (define!=null) {
            if (define!=null) {
                lastDefineEnd = define.getLocation()[1];                
                if (define.getArgs().length!=2)
                    throw new IllegalArgumentException("@define script command must have exactly 2 arguments - variable name and variable value.");
                args.put(define.getArgs()[0], define.getArgs()[1]);
                ret = ret.substring(0, define.getLocation()[0]) + ret.substring((define.getLocation()[1]));
            } 
            define = findMacro(parsed, "@define", lastDefineEnd);            
        }
        return ret;
    }
    
    private class MacroCommand {
        String name;
        String[] args;
        int[] loc;
        
        public MacroCommand(String str, int[] commandLoc) {
            String command = str.substring(commandLoc[0], commandLoc[1]).replaceAll("\\s+", " ");
            loc = commandLoc;
            Tokenizer tokenizer = new Tokenizer(command.substring(1,command.length()-1), ' ');
            String[] tokens = tokenizer.getTokens();
            if (tokens.length>1) {
                args = new String[tokens.length-1];
                System.arraycopy(tokens,1,args,0,args.length);            
            } else args = null;
            name = tokens[0];
        }
        
        public String getName() { return name; }
        public String[] getArgs() { return args; }
        public int[] getLocation() { return loc; }
    }
}
